using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace MoEngine
{
    public struct AnchorInfo
    {
        public Vector2 RelValue;
        public Vector2 AbsValue;
        public RelativeAspect RelTo;

        public Vector2 CalculateFinalValue(Vector2 parentSize)
        {
            switch (RelTo)
            {
                case RelativeAspect.RelToX:
                    return AbsValue + Vector2.Multiply(RelValue, parentSize.X);
                case RelativeAspect.RelToY:
                    return AbsValue + Vector2.Multiply(RelValue, parentSize.Y);
                case RelativeAspect.RelToEach:
                default:
                    return AbsValue + Vector2.Multiply(RelValue, parentSize);
            }
        }
    }

    public enum RelativeAspect
    {
        RelToEach,
        RelToX,
        RelToY,
    }

    public enum AnchorStyle
    {
        ToNear,
        ToFar,
        ToBoth
    }

    public class Layout
    {
        protected AnchorInfo m_TopLeft;
        protected AnchorInfo m_BottomRight;
        protected AnchorInfo m_Size;
        protected AnchorStyle m_AnchorX = AnchorStyle.ToBoth;
        protected AnchorStyle m_AnchorY = AnchorStyle.ToBoth;

        protected Vector2 m_CachedPosition;
        public Vector2 CachedPosition
        {
            get { return m_CachedPosition; }
        }

        protected Vector2 m_CachedSize;
        public Vector2 CachedSize
        {
            get { return m_CachedSize; }
        }

        protected bool m_IsDirty = true;
        public bool IsDirty
        {
            get { return m_IsDirty; }
            set { m_IsDirty = value; }
        }

        public Layout()
        {
            m_BottomRight.RelValue = new Vector2(1, 1);
            m_BottomRight.RelTo = RelativeAspect.RelToEach;
            m_Size.RelValue = new Vector2(1, 1);
            m_Size.RelTo = RelativeAspect.RelToEach;
        }

        public Vector2 TopLeft
        {
            get { return m_TopLeft.AbsValue; }
        }

        public void CalculateLayout(Vector2 parentSize)
        {
            Vector2 topLeft = m_TopLeft.CalculateFinalValue(parentSize);
            Vector2 bottomRight = m_BottomRight.CalculateFinalValue(parentSize);
            Vector2 size = m_Size.CalculateFinalValue(parentSize);

            if (m_AnchorX == AnchorStyle.ToFar)
                m_CachedPosition.X = (float)Math.Round(bottomRight.X - size.X);
            else
                m_CachedPosition.X = (float)Math.Round(topLeft.X);

            if (m_AnchorX == AnchorStyle.ToBoth)
                m_CachedSize.X = (float)Math.Round(bottomRight.X - topLeft.X);
            else
                m_CachedSize.X = (float)Math.Round(size.X);

            if (m_AnchorY == AnchorStyle.ToFar)
                m_CachedPosition.Y = (float)Math.Round(bottomRight.Y - size.Y);
            else
                m_CachedPosition.Y = (float)Math.Round(topLeft.Y);

            if (m_AnchorY == AnchorStyle.ToBoth)
                m_CachedSize.Y = (float)Math.Round(bottomRight.Y - topLeft.Y);
            else
                m_CachedSize.Y = (float)Math.Round(size.Y);

            m_IsDirty = false;
        }

        // helper function to adjust layout
        public virtual void SetLeft(float rel, float abs)
        {
            m_TopLeft.AbsValue.X = abs;
            m_TopLeft.RelValue.X = rel;
            m_IsDirty = true;
        }

        public virtual void SetTop(float rel, float abs)
        {
            m_TopLeft.AbsValue.Y = abs;
            m_TopLeft.RelValue.Y = rel;
            m_IsDirty = true;
        }

        public virtual void SetRight(float rel, float abs)
        {
            m_BottomRight.AbsValue.X = abs;
            m_BottomRight.RelValue.X = rel;
            m_IsDirty = true;
        }

        public virtual void SetBottom(float rel, float abs)
        {
            m_BottomRight.AbsValue.Y = abs;
            m_BottomRight.RelValue.Y = rel;
            m_IsDirty = true;
        }

        public virtual void SetWidth(float rel, float abs, bool bAnchorToRight)
        {
            m_Size.AbsValue.X = abs;
            m_Size.RelValue.X = rel;
            m_AnchorX = bAnchorToRight ? AnchorStyle.ToFar : AnchorStyle.ToNear;
            m_IsDirty = true;
        }

        public virtual void SetHeight(float rel, float abs, bool bAnchorToBottom)
        {
            m_Size.AbsValue.Y = abs;
            m_Size.RelValue.Y = rel;
            m_AnchorY = bAnchorToBottom ? AnchorStyle.ToFar : AnchorStyle.ToNear;
            m_IsDirty = true;
        }
    }

    public class BaseWindowState : State
    {
        public virtual void OnMouseDown(MouseButtons button, Point mousePos, InputEventArgs evtArgs)
        {
        }

        public virtual void OnMouseUp(MouseButtons button, Point mousePos, InputEventArgs evtArgs)
        {
        }

        public virtual void OnMouseMove(Point mousePos, InputEventArgs evtArgs)
        {
        }
    }

    public class WindowState<T> : BaseWindowState where T : Window 
    {
        protected T Window;

        public override void Init(object host, BaseStateManager stateManager)
        {
            base.Init(host, stateManager);
            Window = host as T;
        }
    }

    public class Window : WindowBase
    {
        #region Layout

        protected Vector2 m_Position;
        public override Vector2 Position
        {
            get { return m_Layout.CachedPosition; }
            //set { m_Position = value; }
        }

        protected Vector2 m_Size;
        public override Vector2 Size
        {
            get { return m_Layout.CachedSize; }
            //set { m_Size = value; }
        }

        public Rectangle Rect
        {
            get { return new Rectangle((int)Position.X, (int)Position.Y, (int)Size.X, (int)Size.Y); }
        }

        protected Vector2 m_GlobalPosition;
        public override Vector2 GlobalPosition
        {
            get { return m_GlobalPosition; }
            //set { m_GlobalPosition = value; }
        }

        public Rectangle GlobalRect
        {
            get { return new Rectangle((int)GlobalPosition.X, (int)GlobalPosition.Y, (int)Size.X, (int)Size.Y); }
        }

        protected Layout m_Layout = new Layout();
        public Layout Layout
        {
            get { return m_Layout; }
            //set { m_Layout = value; }
        }

        public bool IsLayoutDirty
        {
            get { return m_bParentSizeChanged || m_bParentGlobalPosChanged || Layout.IsDirty; }
            //set { Layout.IsDirty = value; }
        }

        public override void ValidateLayout()
        {
            if (IsLayoutDirty)
            {
                Vector2 oldSize = Size;
                Vector2 oldGlobalPos = GlobalPosition;

                if (m_bParentSizeChanged || Layout.IsDirty)
                {
                    Layout.CalculateLayout(Parent.Size);
                    if (oldSize != Size)
                        OnSizeChanged();
                }

                m_GlobalPosition = Parent.GlobalPosition + Position;

                if (oldGlobalPos != GlobalPosition)
                    OnGlobalPositionChanged();

                m_bParentSizeChanged = false;
                m_bParentGlobalPosChanged = false;

                OnLayoutValidated();
            }
        }

        protected Rectangle m_DrawRect;

        public virtual Rectangle DrawRect
        {
            get { return m_DrawRect; }
        }

        public virtual Vector2 DrawPos
        {
            get { return new Vector2(m_DrawRect.X, m_DrawRect.Y); }
        }

        protected virtual void OnLayoutValidated() 
        {
            m_DrawRect.X = (int)Math.Round(GlobalPosition.X);
            m_DrawRect.Y = (int)Math.Round(GlobalPosition.Y);
            // TODO: clamp size to current display resolution and within parent window
            m_DrawRect.Width = (int)Math.Round(Size.X);
            m_DrawRect.Height = (int)Math.Round(Size.Y);
        }

        protected bool m_bParentSizeChanged = true;
        protected bool m_bParentGlobalPosChanged = true;

        void OnParentSizeChanged(object sender, EventArgs e)
        {
            m_bParentSizeChanged = true;
        }

        void OnParentGlobalPoistionChanged(object sender, EventArgs e)
        {
            m_bParentGlobalPosChanged = true;
        }

        #endregion

        
        protected WindowBase m_Parent;
        public WindowBase Parent
        {
            get { return m_Parent; }            
        }

        public void OnAttachedToParent(WindowBase newParent)
        {
            newParent.GlobalPoistionChanged += new EventHandler(OnParentGlobalPoistionChanged);
            newParent.SizeChanged += new EventHandler(OnParentSizeChanged);
        }


        public void OnDetachedFromParent(WindowBase oldParent)
        {
            oldParent.GlobalPoistionChanged -= OnParentGlobalPoistionChanged;
            oldParent.SizeChanged -= OnParentSizeChanged;
        }


        /*
        protected Name m_Name;
        public Name Name
        {
            get { return m_Name; }
            set
            {
                // TODO: more explicit error 
                // when trying to reset name
                // Or should we allow it?
                if (m_Name != null)
                    return;

                m_Name = value;               

            }
        }*/
        protected MasterWindow m_MasterWindow;

        public override MasterWindow MasterWnd
        {
            get { return m_MasterWindow; }
        }

        public override GraphicsDevice Device
        {
            get { return MasterWnd.Device; }
        }

        protected SpriteBatch m_SpriteBatch;

        public Window(WindowBase parent)
        {
            // TODO: assert parent != null

            m_Parent = parent;
            m_MasterWindow = m_Parent.MasterWnd;

            m_Parent.AddChild(this);

            // create graphics content
            m_SpriteBatch = new SpriteBatch(Core.GlobalDevice);

            LoadContent();

            // setup window states
            InitWindowStates();
        }

        protected virtual void LoadContent() { }

        protected virtual void InitWindowStates() { }


        public virtual BaseWindowState CurrentState
        {
            get { return null; }
        }
       
        protected override void UpdateWindow(GameTime time)
        {
            base.UpdateWindow(time);

            // clear handled events
            m_HandledInputEvents.Clear();

            // update current state
            if (CurrentState != null)
                CurrentState.Update(time);
        }

        protected override void DrawWindow(GameTime time)
        {
            // todo: draw current state?
            base.DrawWindow(time);
        }

        protected virtual void BeginDraw()
        {
            m_SpriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
            Core.GlobalDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            Core.GlobalDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;           
        }

        protected virtual void EndDraw()
        {
            m_SpriteBatch.End();
        }

        protected bool m_bReceiveInput = true;

        protected bool m_bTrackMouseEnterExit = false;
        protected bool m_bMouseEntered = false;

        protected bool m_bPressed;

        protected List<InputEventArgs> m_HandledInputEvents = new List<InputEventArgs>();

        public override bool HandleMouseInput(MouseEventType evtType, MouseButtons button, Point mousePos, InputEventArgs evtArgs)
        {
            ValidateLayout();

            if (Rect.Contains(mousePos) && m_bReceiveInput)
            {
                // cache handled event
                if (!m_HandledInputEvents.Contains(evtArgs))
                {
                    m_HandledInputEvents.Add(evtArgs);
                    
                    // call event based on evtType
                    switch (evtType)
                    {
                        case MouseEventType.MouseDown:
                            OnMouseDown(button, mousePos, evtArgs);
                            break;
                        case MouseEventType.MouseUp:
                            OnMouseUp(button, mousePos, evtArgs);
                            break;
                        case MouseEventType.MouseMove:
                            OnMouseMove(mousePos, evtArgs);
                            break;
                    }
                }  
             
                // TODO: check if consumed

                // translate mouse pos into current frame
                mousePos.X -= (int)Position.X;
                mousePos.Y -= (int)Position.Y;
                // add children
                m_ChildWnds.HandleMouseInput(evtType, button, mousePos, evtArgs);

                return true;
            }

            return false; 
        }

        public virtual void OnMouseMove(Point mousePos, InputEventArgs evtArgs)
        {
            if (m_bTrackMouseEnterExit && !m_bMouseEntered)
                OnEnter(evtArgs);

            if (CurrentState != null)
                CurrentState.OnMouseMove(mousePos, evtArgs);
        }

        public virtual void OnMouseDown(MouseButtons button, Point mousePos, InputEventArgs evtArgs)
        {
            m_bPressed = true;

            // Console.WriteLine("mouse down: {0}", mousePos);
            if (CurrentState != null)
                CurrentState.OnMouseDown(button, mousePos, evtArgs);
        }

        public virtual void OnMouseUp(MouseButtons button, Point mousePos, InputEventArgs evtArgs)
        {
            if (CurrentState != null)
                CurrentState.OnMouseUp(button, mousePos, evtArgs);

            if (m_bPressed)
            {
                OnClick(button, mousePos, evtArgs);
                m_bPressed = false;
            }
        }

        public virtual void OnEnter(InputEventArgs evtArgs)
        {
            m_bMouseEntered = true;
            Core.InputMgr.MouseMove += new MouseMoveEvent(OnTrackingMouseExit);
        }

        void OnTrackingMouseExit(Point mousePos, InputEventArgs evtArgs)
        {
            if (!GlobalRect.Contains(mousePos))
            {
                OnExit(evtArgs);
            }
        }

        public virtual void OnExit(InputEventArgs evtArgs)
        {
            m_bPressed = false;
            m_bMouseEntered = false;
            Core.InputMgr.MouseMove -= OnTrackingMouseExit;
        }

        public event EventHandler<InputEventArgs> Click;

        public virtual void OnClick(MouseButtons button, Point mousePos, InputEventArgs evtArgs)
        {
            if (Click != null)
            {
                Click(this, evtArgs);
            }
        }
    }
}
